package com.ksyun.campus.client.service;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ksyun.campus.client.FSInputStream;
import com.ksyun.campus.client.FSOutputStream;
import com.ksyun.campus.client.common.constant.ServerConfiguration;
import com.ksyun.campus.client.common.context.ServerContext;
import com.ksyun.campus.client.domain.*;
import com.ksyun.campus.client.domain.dto.WriteReqDto;
import com.ksyun.campus.client.domain.server.DataServer;
import com.ksyun.campus.client.domain.server.MetaServer;
import com.ksyun.campus.client.util.HttpClientConfig;
import com.ksyun.campus.client.util.HttpClientUtil;
import com.ksyun.campus.client.util.ZkUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class ClientToMetaService {
    public ZkUtil zkUtil=new ZkUtil();
//    private String MetaServerUrl="http://localhost:8001";
    public String DataServerUrl="http://localhost:9000";

    /**
     * 动态获取MetaServerUrl
     * @return ip:port
     */
    public String GetMetaUrlFromZK(){
        List<MetaServer> metaServerList = ServerContext.getMetaServerList();
        // TODO: 2024/8/18 检查初始化ServerContext是否是List（）
        for (MetaServer m:metaServerList){
            if(m.getIsMaster()){
//                System.out.println("从ThreadLocal获得向"+m+"发送metaServer请求");
                return m.getIp()+":"+m.getPort();
            }
        }
        // TODO: 2024/8/19 或者throw 找不到metaServer
        //保底向8001发送MetaServer请求
        return "http://localhost:8001";
    }


    /**
     * 向metaserver发送open文件请求,得到FSInputStream
     * @param defaultFileSystemName
     * @param path
     * @return FSInputSteam
     */
    public FSInputStream open(String defaultFileSystemName, String path) {
        //判断是目录直接返回null
        if(!isFilePath(path)){return null;}
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        //向MetaServer查询三副本的位置，
        String url = MetaServerUrl+ServerConfiguration.METAOPENPATH + path;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("fileSystemName", defaultFileSystemName);

        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();

            if (statusCode == HttpStatus.SC_OK) {
                String responseBody = EntityUtils.toString(response.getEntity());
                BaseResult<StatInfo> result = new ObjectMapper().readValue(responseBody
                        , new TypeReference<BaseResult<StatInfo>>() {});
                if (result.getCode() == 200) {
                    StatInfo statInfo = result.getData();
                    FSInputStream fsInputStream=FSInputStream.builder()
                            .statInfo(statInfo).path(path)
                            .defaultFileSystemName(defaultFileSystemName)
                            .offset(0)
                            .build();
//                    log.info("创建文件输入流,open->FSInputStream");
                    return fsInputStream;
                } else {
                    throw new IOException("Business error occurred: " + result.getMsg());
                }
            } else {
                throw new IOException("Failed to open file: " + path + " with HTTP status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 判断这个path是否是文件 而不是目录
     * @param path
     * @return
     */
    private boolean isFilePath(String path) {
        // 判断路径中是否包含文件扩展名（简单的假设）
        int lastDotIndex = path.lastIndexOf('.');
        int lastSlashIndex = path.lastIndexOf('/');
        return lastSlashIndex<lastDotIndex;
    }

    /**
     * 向metaServer发送创建文件请求
     * @param defaultFileSystemName
     * @param path
     * @return 文件输出流FSOutputSteam
     */
    public FSOutputStream create(String defaultFileSystemName, String path) {
        //判断是目录直接返回null
        if(!isFilePath(path)){return null;}
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ ServerConfiguration.METACREATEPATH + path;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("fileSystemName", defaultFileSystemName);
        //向MetaServer发送create请求
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            if (statusCode == HttpStatus.SC_OK) {
                String responseBody = EntityUtils.toString(response.getEntity());
                BaseResult<StatInfo> result = new ObjectMapper().readValue(responseBody,
                        new TypeReference<BaseResult<StatInfo>>() {});

                //得到MetaServer返回的元数据statInfo
                if (result.getCode() == 200) {
                    StatInfo statInfo = result.getData();
                    FSOutputStream fsOutputStream=FSOutputStream.builder()
                            .statInfo(statInfo).path(path)
                            .defaultFileSystemName(defaultFileSystemName)
                            .buffer(new ByteArrayOutputStream())
                            .metaServerUrl(MetaServerUrl)
                            .build();
//                    log.info("创建文件输出流,create->FSOutputStream");
                    return fsOutputStream;
                } else {
                    throw new IOException("Meta Business error: " + result.getMsg());
                }
            } else {
                throw new IOException("Failed to create file: " + path + " with HTTP status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *  向metaServer发送创建文件夹请求
     * @param defaultFileSystemName
     * @param path
     * @return 是否创建成功
     */
    public Boolean mkdir(String defaultFileSystemName, String path) {
        //判断是不是目录直接返回null
        if(isFilePath(path)){return null;}
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ServerConfiguration.METAMKDIRPATH+ path;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("fileSystemName", defaultFileSystemName);
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            //metaServer返回创建"目录"的元数据StatInfo
            if (statusCode == HttpStatus.SC_OK) {
                String responseBody = EntityUtils.toString(response.getEntity());
                BaseResult<StatInfo> result = new ObjectMapper().readValue(responseBody,
                        new TypeReference<BaseResult<StatInfo>>() { });

                if (result.getCode() == 200) {
//                    System.out.println("目录创建成功:元数据信息为"+result.getData());
                    //向dataServer发送创建目录请求
                    MkdirFromDataServer(result.getData(),defaultFileSystemName);
                    return true; // 目录创建成功
                } else {
                    throw new IOException("Meta Business error : " + result.getMsg());
                }
            } else {
                throw new IOException("Failed to create directory: " + path + " with HTTP status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * client向dataServer发送创建文件夹请求(write)
     *
     * @param statInfo
     * @param FileSystemName
     * @return 返回是否创建成功
     * @throws IOException
     */
    private Boolean MkdirFromDataServer(StatInfo statInfo,String FileSystemName) throws IOException {
        ReplicaData replicaData = statInfo.getReplicaData().get(0);
        String ipAndPort = replicaData.getDsNode();
        String dataServerUrl=ServerConfiguration.HTTP+ipAndPort+ ServerConfiguration.DATAWRITEPATH;
        HttpPost dataPost=new HttpPost(dataServerUrl);
        dataPost.addHeader("fileSystemName", FileSystemName);
        ContentType contentType=ContentType.APPLICATION_JSON;
        byte[] bytes = new byte[0];
        WriteReqDto writeReqDto=new WriteReqDto(statInfo,bytes,0,bytes.length);

        // 创建WriteReqDto的StringEntity 并设置到 HttpPost 请求中
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonReqBody = objectMapper.writeValueAsString(writeReqDto);
        StringEntity entity = new StringEntity(jsonReqBody, contentType);
        dataPost.setEntity(entity);

        //与dataServer通信创建文件夹后，拿到StatInfo
        HttpClient ClientToDS=HttpClientUtil.createHttpClient(new HttpClientConfig());
        try (CloseableHttpResponse responseDS=(CloseableHttpResponse) ClientToDS.execute(dataPost)) {
            if (responseDS.getCode() == HttpStatus.SC_OK) {
                String resBodyDS = EntityUtils.toString(responseDS.getEntity());
                BaseResult<StatInfo> result = new ObjectMapper().readValue(resBodyDS
                        , new TypeReference<BaseResult<StatInfo>>() {});
                if(result.getCode()==200){
//                    System.out.println("从dataServer创建文件夹成功");
                    return true;
                }else {
                    throw new IOException("DataServer Business error: "+result.getMsg());
                }
            }else {
                throw new IOException("Failed to  mkdir from MetaServer with HTTP status code: "
                        + responseDS.getCode());
            }
        }catch (Exception e) {
            e.printStackTrace();
            throw new IOException("Failed to mkdir to DataServer", e);
        }
    }

    /**
     *  delete: 1.向metaServer和dataServer先后发送delete请求
     * @param defaultFileSystemName
     * @param path
     * @return 是否删除成功
     */
    public Boolean delete(String defaultFileSystemName, String path) {
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ServerConfiguration.METADELETEPATH+ path;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("fileSystemName", defaultFileSystemName);
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            if (statusCode == HttpStatus.SC_OK) {
                String responseBody = EntityUtils.toString(response.getEntity());
                BaseResult<StatInfo> result = new ObjectMapper().readValue(responseBody,
                        new TypeReference<BaseResult<StatInfo>>() { });

                if (result.getCode() == 200) {

//                    System.out.println("文件元数据删除成功:元数据信息为"+result.getData());
                    Boolean isDelete=deleteFromDataServer(result.getData(),defaultFileSystemName);
                    if(!isDelete){
                        DeleteCallback(result.getData(),defaultFileSystemName);
                    }
                    return isDelete;
                } else {
                    throw new IOException("metaServer Business error occurred: " + result.getMsg());
                }
            } else {
                throw new IOException("Failed to delete from MetaServer: " + path
                        + " with HTTP status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * delete: 2.client向dataServer发送删除请求
     *
     * @param statInfo
     * @param FileSystemName
     * @return 返回是否删除成功
     */
    private Boolean deleteFromDataServer(StatInfo statInfo,String FileSystemName) throws IOException {
        ReplicaData replicaData = statInfo.getReplicaData().get(0);
        String ipAndPort = replicaData.getDsNode();
        String dataServerUrl=ServerConfiguration.HTTP+ipAndPort+ ServerConfiguration.DATADELETEPATH;
        HttpPost dataPost=new HttpPost(dataServerUrl);
        dataPost.addHeader("fileSystemName", FileSystemName);
        ContentType contentType=ContentType.APPLICATION_JSON;
        byte[] bytes = new byte[0];
        WriteReqDto writeReqDto=new WriteReqDto(statInfo,bytes,0,bytes.length);

        // 创建WriteReqDto的StringEntity 并设置到 HttpPost 请求中
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonReqBody = objectMapper.writeValueAsString(writeReqDto);
        StringEntity entity = new StringEntity(jsonReqBody, contentType);
        dataPost.setEntity(entity);

        //与dataServer通信删文件后，拿到StatInfo
        HttpClient ClientToDS=HttpClientUtil.createHttpClient(new HttpClientConfig());
        try (CloseableHttpResponse responseDS=(CloseableHttpResponse) ClientToDS.execute(dataPost)) {
            if (responseDS.getCode() == HttpStatus.SC_OK) {
                String resBodyDS = EntityUtils.toString(responseDS.getEntity());
                BaseResult<StatInfo> result = new ObjectMapper().readValue(resBodyDS
                        , new TypeReference<BaseResult<StatInfo>>() {});
                if(result.getCode()==200){
//                    System.out.println("从dataServer删除文件成功");
                    return true;
                }else {
                    throw new IOException("Failed to delete from DataServer"+result.getMsg());
                }
            }else {
                throw new IOException("Failed to commit delete from DataServer with HTTP status code: "
                        + responseDS.getCode());
            }
        }catch (Exception e) {
            e.printStackTrace();
            throw new IOException("Failed to delete from DataServer", e);
        }
    }

    /**
     * delete: 3.向MetaServer回调删除失败,传入之前的path文件元数据statInfo
     * @param data
     * @param defaultFileSystemName
     */
    private void DeleteCallback(StatInfo data, String defaultFileSystemName) throws Exception {
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ServerConfiguration.METADELETECALLBACK;

        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpPost httpPost = new HttpPost(url);
        httpPost.addHeader("fileSystemName", defaultFileSystemName);

        ObjectMapper objectMapper = new ObjectMapper();
        String jsonReqBodyMS = objectMapper.writeValueAsString(data);
        StringEntity entityMS = new StringEntity(jsonReqBodyMS, ContentType.APPLICATION_JSON);
        httpPost.setEntity(entityMS);
        //向MetaServer回调删除失败,传入之前的path文件元数据statInfo
        httpClient.execute(httpPost);
    }


    /**
     *  获取单个文件元数据
     *
     * @param defaultFileSystemName
     * @param path
     * @return StatInfo
     */
    public StatInfo getFileStats(String defaultFileSystemName, String path) {
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ServerConfiguration.METASTATSPATH+ path;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("fileSystemName", defaultFileSystemName);
        //向metaServer发送stats请求 获得单个文件元数据信息StatInfo
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            if (statusCode == HttpStatus.SC_OK) {
                String responseBody = EntityUtils.toString(response.getEntity());
                BaseResult<StatInfo> result=new ObjectMapper().readValue(responseBody
                        , new TypeReference< BaseResult<StatInfo>>() {});
                if(result.getCode()==200){
//                    System.out.println("成功向metaServer获取该文件元数据信息:"+result.getData());
                    return result.getData();
                }else {
//                    System.out.println("失败向metaServer获取该文件元数据信息");
                    return null;
                }
            } else {
                throw new IOException("Failed to get file stats: " + path + " with status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获得该目录下文件元数据信息
     *
     * @param defaultFileSystemName
     * @param path
     * @return 目录下文件元数据信息:List<StatInfo>
     */
    public List<StatInfo> listFileStats(String defaultFileSystemName, String path) {
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ServerConfiguration.METALISTDIRPATH+ path;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("fileSystemName", defaultFileSystemName);
        //向metaServer发送stats请求 获得该目录下文件元数据信息List<StatInfo>
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            if (statusCode == HttpStatus.SC_OK) {
                String responseBody = EntityUtils.toString(response.getEntity());
                BaseResult<List<StatInfo>> result=new ObjectMapper().readValue(responseBody
                        , new TypeReference< BaseResult<List<StatInfo>>>() {});
                if(result.getCode()==200){
//                    System.out.println("成功向metaServer获取该目录下文件元数据信息:"+result.getData());
                    return result.getData();
                }else {
//                    System.out.println("失败向metaServer获取该目录下文件元数据信息");
                    return null;
                }
            } else {
                throw new IOException("Failed to get file stats: " + path + " with status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public ClusterInfo getClusterInfo() {
        try {
            CuratorFramework client = zkUtil.client;
            ClusterInfo clusterInfo = new ClusterInfo();
            //从zookeeper中获取metaServer
            List<String> metaStringList = client.getChildren().forPath(ServerConfiguration.ZKMETASERVERPATH);
            List<MetaServer> metaServerList=metaStringList.stream().map(each->{
                try {
                    String childrenPath=ServerConfiguration.ZKMETASERVERPATH+"/"+each;
                    byte[] MetaServerBytes = client.getData().forPath(childrenPath);
//                    System.out.println("从zookeeper中获取MetaServer的信息:"+new String(MetaServerBytes));
                    return JSON.parseObject(MetaServerBytes, MetaServer.class);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }).collect(Collectors.toList());
            //设置主从metaServerMsg
            for(MetaServer meta:metaServerList){
                if(meta.getIsMaster()){
                    MetaServerMsg master = new MetaServerMsg();
                    master.setHost(meta.getIp());
                    master.setPort(meta.getPort());
                    clusterInfo.setMasterMetaServer(master);
                }else {
                    MetaServerMsg slave = new MetaServerMsg();
                    slave.setHost(meta.getIp());
                    slave.setPort(meta.getPort());
                    clusterInfo.setSlaveMetaServer(slave);
                }
            }

            //从zookeeper中获取dataServer
            List<String> dataStringList = client.getChildren().forPath(ServerConfiguration.ZKDATASERVERPATH);
            List<DataServer> dataServerList=dataStringList.stream().map(each->{
                try {
                    String childrenPath=ServerConfiguration.ZKDATASERVERPATH+"/"+each;
                    byte[] DataServerBytes = client.getData().forPath(childrenPath);
//                    System.out.println("从zookeeper中获取DataServer的信息:"+new String(DataServerBytes));
                    return JSON.parseObject(DataServerBytes, DataServer.class);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }).collect(Collectors.toList());
            List<DataServerMsg> dataServerMsgList = new ArrayList<>();
            for(DataServer d:dataServerList){
                DataServerMsg dataServerMsg = new DataServerMsg();
                dataServerMsg.setHost(d.getIp());
                dataServerMsg.setPort(d.getPort());
                dataServerMsg.setFileTotal(d.getFileTotal());
                dataServerMsg.setCapacity(d.getCapacity());
                dataServerMsg.setUseCapacity(d.getUsed());
                dataServerMsgList.add(dataServerMsg);
            }
            //设置dataServer列表
            clusterInfo.setDataServer(dataServerMsgList);
            return clusterInfo;

        } catch (Exception e) {
//            System.out.println("失败连接ZooKeeper"+ e);
        }
        return  null;
    }

    /**
     * 关闭metaServer(测试模拟时使用)
     * @return 是否关闭成功
     */
    public Boolean shutDownMetaServer(){
        String MetaServerUrl=ServerConfiguration.HTTP+GetMetaUrlFromZK();
        String url = MetaServerUrl+ServerConfiguration.METASHUTDOWNPATH;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            if (statusCode == HttpStatus.SC_OK) {
//                System.out.println("成功关闭metaServer");
                return true;
            } else {

                throw new IOException("Failed to shutdown metaServer" + " with HTTP status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 关闭dataServer(测试模拟时使用)
     * @return 是否关闭成功
     */
    public Boolean shutDownDataServer(){
        String url = DataServerUrl+ServerConfiguration.DATASHUTDOWNPATH;
        HttpClient httpClient = HttpClientUtil.createHttpClient(new HttpClientConfig());
        HttpGet httpGet = new HttpGet(url);
        try (CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            int statusCode = response.getCode();
            if (statusCode == HttpStatus.SC_OK) {
//                System.out.println("成功关闭dataServer");
                return true;
            } else {

                throw new IOException("Failed to shutdown dataServer" + " with HTTP status code: " + statusCode);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
